/*-
 * Copyright (c) 2011-2013 Kai Wang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ld.h"
#include "ld_exp.h"
#include "ld_options.h"
#include "ld_script.h"
#include "ld_file.h"
#include "ld_symbols.h"

ELFTC_VCSID("$Id: ld_script.c 2881 2013-01-09 22:46:54Z kaiwang27 $");

static void _input_file_add(struct ld *ld, struct ld_script_input_file *ldif);
static void _overlay_section_free(void *ptr);
static struct ld_script_variable *_variable_find(struct ld *ld, char *name);

#define _variable_add(v) \
	HASH_ADD_KEYPTR(hh, ld->ld_scp->lds_v, (v)->ldv_name, \
	    strlen((v)->ldv_name), (v))

struct ld_script_cmd *
ld_script_assert(struct ld *ld, struct ld_exp *exp, char *msg)
{
	struct ld_script_assert *a;

	if ((a = calloc(1, sizeof(*a))) == NULL)
		ld_fatal_std(ld, "calloc");
	a->lda_exp = exp;
	a->lda_msg = msg;

	return (ld_script_cmd(ld, LSC_ASSERT, a));
}

struct ld_script_assign *
ld_script_assign(struct ld *ld, struct ld_exp *var, enum ld_script_assign_op op,
    struct ld_exp *val, unsigned provide, unsigned hidden)
{
	struct ld_script_assign *lda;
	struct ld_script_variable *ldv;

	if ((lda = calloc(1, sizeof(*lda))) == NULL)
		ld_fatal_std(ld, "calloc");

	lda->lda_var = var;
	lda->lda_op = op;
	lda->lda_val = val;
	lda->lda_provide = provide;

	if ((ldv = _variable_find(ld, var->le_name)) == NULL) {
		ldv = calloc(1, sizeof(*ldv));
		if ((ldv->ldv_name = strdup(var->le_name)) == NULL)
			ld_fatal_std(ld, "strdup");
		_variable_add(ldv);
		if (*var->le_name != '.')
			ld_symbols_add_variable(ld, ldv, provide, hidden);
	}

	return (lda);
}

void
ld_script_assign_dump(struct ld *ld, struct ld_script_assign *lda)
{

	printf("%16s", "");
	printf("0x%016jx ", (uintmax_t) lda->lda_res);

	if (lda->lda_provide)
		printf("PROVIDE(");

	ld_exp_dump(ld, lda->lda_var);

	switch (lda->lda_op) {
	case LSAOP_ADD_E:
		printf(" += ");
		break;
	case LSAOP_AND_E:
		printf(" &= ");
		break;
	case LSAOP_DIV_E:
		printf(" /= ");
		break;
	case LSAOP_E:
		printf(" = ");
		break;
	case LSAOP_LSHIFT_E:
		printf(" <<= ");
		break;
	case LSAOP_MUL_E:
		printf(" *= ");
		break;
	case LSAOP_OR_E:
		printf(" |= ");
		break;
	case LSAOP_RSHIFT_E:
		printf(" >>= ");
		break;
	case LSAOP_SUB_E:
		printf(" -= ");
		break;
	default:
		ld_fatal(ld, "internal: unknown assignment op: %d",
		    lda->lda_op);
	}

	ld_exp_dump(ld, lda->lda_val);

	if (lda->lda_provide)
		printf(")");

	printf("\n");
}

void
ld_script_assign_free(struct ld_script_assign *lda)
{

	if (lda == NULL)
		return;
	ld_exp_free(lda->lda_var);
	ld_exp_free(lda->lda_val);
	free(lda);
}

void
ld_script_process_assign(struct ld *ld, struct ld_script_assign *lda)
{
	struct ld_state *ls;
	struct ld_exp *var;
	struct ld_script_variable *ldv;

	ls = &ld->ld_state;
	var = lda->lda_var;
	ldv = _variable_find(ld, var->le_name);
	assert(ldv != NULL);

	ldv->ldv_val = ld_exp_eval(ld, lda->lda_val);
	if (*var->le_name == '.') {
		/*
		 * TODO: location counter is allowed to move backwards
		 * outside output section descriptor, as long as the
		 * move will not cause overlapping LMA's.
		 */
		if ((uint64_t) ldv->ldv_val < ls->ls_loc_counter)
			ld_fatal(ld, "cannot move location counter backwards"
			    " from %#jx to %#jx",
			    (uintmax_t) ls->ls_loc_counter,
			    (uintmax_t) ldv->ldv_val);
		ls->ls_loc_counter = (uint64_t) ldv->ldv_val;
	}
	lda->lda_res = ldv->ldv_val;
}

void
ld_script_process_entry(struct ld *ld, char *name)
{

	if (ld->ld_scp->lds_entry_point != NULL) {
		free(ld->ld_scp->lds_entry_point);
		ld->ld_scp->lds_entry_point = NULL;
	}

	ld->ld_scp->lds_entry_point = strdup(name);
	if (ld->ld_scp->lds_entry_point == NULL)
		ld_fatal_std(ld, "strdup");
}

int64_t
ld_script_variable_value(struct ld *ld, char *name)
{
	struct ld_script_variable *ldv;
	struct ld_state *ls;

	ls = &ld->ld_state;
	if (*name == '.')
		return (ls->ls_loc_counter);

	ldv = _variable_find(ld, name);
	assert(ldv != NULL);

	return (ldv->ldv_val);
}

struct ld_script_cmd *
ld_script_cmd(struct ld *ld, enum ld_script_cmd_type type, void *cmd)
{
	struct ld_script_cmd *ldc;

	if ((ldc = calloc(1, sizeof(*ldc))) == NULL)
		ld_fatal_std(ld, "calloc");
	ldc->ldc_type = type;
	ldc->ldc_cmd = cmd;

	return (ldc);
}

void
ld_script_cmd_insert(struct ld_script_cmd_head *head, struct ld_script_cmd *ldc)
{

	STAILQ_INSERT_TAIL(head, ldc, ldc_next);
}

static void
_overlay_section_free(void *ptr)
{
	struct ld_script_cmd *c, *_c;
	struct ld_script_sections_overlay_section *ldos;

	ldos = ptr;
	if (ldos == NULL)
		return;
	free(ldos->ldos_name);
	ld_script_list_free(ldos->ldos_phdr, free);
	ld_exp_free(ldos->ldos_fill);
	STAILQ_FOREACH_SAFE(c, &ldos->ldos_c, ldc_next, _c) {
		STAILQ_REMOVE(&ldos->ldos_c, c, ld_script_cmd, ldc_next);
		ld_script_cmd_free(c);
	}
	free(ldos);
}

void
ld_script_cmd_free(struct ld_script_cmd *ldc)
{
	struct ld_script_cmd *c, *_c;
	struct ld_script_assert *lda;
	struct ld_script_sections *ldss;
	struct ld_script_sections_output *ldso;
	struct ld_script_sections_output_data *ldod;
	struct ld_script_sections_output_input *ldoi;
	struct ld_script_sections_overlay *ldso2;

	switch (ldc->ldc_type) {
	case LSC_ASSERT:
		lda = ldc->ldc_cmd;
		ld_exp_free(lda->lda_exp);
		free(lda->lda_msg);
		free(lda);
		break;

	case LSC_ASSIGN:
		ld_script_assign_free(ldc->ldc_cmd);
		break;

	case LSC_ENTRY:
		free(ldc->ldc_cmd);
		break;

	case LSC_SECTIONS:
		ldss = ldc->ldc_cmd;
		STAILQ_FOREACH_SAFE(c, &ldss->ldss_c, ldc_next, _c) {
			STAILQ_REMOVE(&ldss->ldss_c, c, ld_script_cmd,
			    ldc_next);
			ld_script_cmd_free(c);
		}
		free(ldss);
		break;

	case LSC_SECTIONS_OUTPUT:
		ldso = ldc->ldc_cmd;
		free(ldso->ldso_name);
		free(ldso->ldso_type);
		ld_exp_free(ldso->ldso_vma);
		ld_exp_free(ldso->ldso_lma);
		ld_exp_free(ldso->ldso_align);
		ld_exp_free(ldso->ldso_subalign);
		free(ldso->ldso_constraint);
		free(ldso->ldso_region);
		free(ldso->ldso_lma_region);
		ld_script_list_free(ldso->ldso_phdr, free);
		ld_exp_free(ldso->ldso_fill);
		STAILQ_FOREACH_SAFE(c, &ldso->ldso_c, ldc_next, _c) {
			STAILQ_REMOVE(&ldso->ldso_c, c, ld_script_cmd,
			    ldc_next);
			ld_script_cmd_free(c);
		}
		free(ldso);
		break;

	case LSC_SECTIONS_OUTPUT_DATA:
		ldod = ldc->ldc_cmd;
		ld_exp_free(ldod->ldod_exp);
		free(ldod);
		break;

	case LSC_SECTIONS_OUTPUT_INPUT:
		ldoi = ldc->ldc_cmd;
		ld_wildcard_free(ldoi->ldoi_ar);
		ld_wildcard_free(ldoi->ldoi_file);
		ld_script_list_free(ldoi->ldoi_exclude, ld_wildcard_free);
		ld_script_list_free(ldoi->ldoi_sec, ld_wildcard_free);
		free(ldoi);
		break;
		
	case LSC_SECTIONS_OVERLAY:
		ldso2 = ldc->ldc_cmd;
		ld_exp_free(ldso2->ldso_vma);
		ld_exp_free(ldso2->ldso_lma);
		free(ldso2->ldso_region);
		ld_script_list_free(ldso2->ldso_phdr, free);
		ld_exp_free(ldso2->ldso_fill);
		ld_script_list_free(ldso2->ldso_s, _overlay_section_free);
		free(ldso2);
		break;

	default:
		break;
	}

	free(ldc);
}

void
ld_script_extern(struct ld *ld, struct ld_script_list *list)
{
	struct ld_script_list *ldl;

	ldl = list;
	while (ldl != NULL) {
		ld_symbols_add_extern(ld, ldl->ldl_entry);
		ldl = ldl->ldl_next;
	}
	ld_script_list_free(list, free);
}

void
ld_script_group(struct ld *ld, struct ld_script_list *list)
{
	struct ld_script_list *ldl;

	ld->ld_state.ls_group_level++;
	if (ld->ld_state.ls_group_level > LD_MAX_NESTED_GROUP)
		ld_fatal(ld, "too many nested archive groups");
	ldl = list;
	while (ldl != NULL) {
		_input_file_add(ld, ldl->ldl_entry);
		ldl = ldl->ldl_next;
	}
	ld->ld_state.ls_group_level--;
	ld_script_list_free(list, free);
}

void
ld_script_init(struct ld *ld)
{

	ld->ld_scp = calloc(1, sizeof(*ld->ld_scp));
	if (ld->ld_scp == NULL)
		ld_fatal_std(ld, "calloc");

	STAILQ_INIT(&ld->ld_scp->lds_a);
	STAILQ_INIT(&ld->ld_scp->lds_c);
	STAILQ_INIT(&ld->ld_scp->lds_n);
	STAILQ_INIT(&ld->ld_scp->lds_p);
	STAILQ_INIT(&ld->ld_scp->lds_r);
	STAILQ_INIT(&ld->ld_scp->lds_vn);

	ld_script_parse_internal();
}

void
ld_script_cleanup(struct ld *ld)
{
	struct ld_script *lds;
	struct ld_script_phdr *p, *_p;
	struct ld_script_region *r, *_r;
	struct ld_script_region_alias *a, *_a;
	struct ld_script_nocrossref *n, *_n;
	struct ld_script_cmd *c, *_c;
	struct ld_script_variable *v, *_v;

	if (ld->ld_scp == NULL)
		return;

	lds = ld->ld_scp;

	if (lds->lds_entry_point != NULL) {
		free(lds->lds_entry_point);
		lds->lds_entry_point = NULL;
	}

	STAILQ_FOREACH_SAFE(p, &lds->lds_p, ldsp_next, _p) {
		STAILQ_REMOVE(&lds->lds_p, p, ld_script_phdr, ldsp_next);
		free(p->ldsp_name);
		free(p->ldsp_type);
		ld_exp_free(p->ldsp_addr);
		free(p);
	}

	STAILQ_FOREACH_SAFE(r, &lds->lds_r, ldsr_next, _r) {
		STAILQ_REMOVE(&lds->lds_r, r, ld_script_region, ldsr_next);
		free(r->ldsr_name);
		free(r->ldsr_attr);
		ld_exp_free(r->ldsr_origin);
		ld_exp_free(r->ldsr_len);
		free(r);
	}

	STAILQ_FOREACH_SAFE(a, &lds->lds_a, ldra_next, _a) {
		STAILQ_REMOVE(&lds->lds_a, a, ld_script_region_alias,
		    ldra_next);
		free(a->ldra_alias);
		free(a->ldra_region);
		free(a);
	}

	STAILQ_FOREACH_SAFE(n, &lds->lds_n, ldn_next, _n) {
		STAILQ_REMOVE(&lds->lds_n, n, ld_script_nocrossref, ldn_next);
		ld_script_list_free(n->ldn_l, free);
		free(n);
	}

	STAILQ_FOREACH_SAFE(c, &lds->lds_c, ldc_next, _c) {
		STAILQ_REMOVE(&lds->lds_c, c, ld_script_cmd, ldc_next);
		ld_script_cmd_free(c);
	}

	if (lds->lds_v != NULL) {
		HASH_ITER(hh, lds->lds_v, v, _v) {
			HASH_DEL(lds->lds_v, v);
			free(v->ldv_name);
			free(v);
		}
		lds->lds_v = NULL;
	}
}

void
ld_script_input(struct ld *ld, struct ld_script_list *list)
{
	struct ld_script_list *ldl;

	ld->ld_state.ls_search_dir = 1;
	ldl = list;
	while (ldl != NULL) {
		_input_file_add(ld, ldl->ldl_entry);
		ldl = ldl->ldl_next;
	}
	ld->ld_state.ls_search_dir = 0;
	ld_script_list_free(list, free);
}

struct ld_script_input_file *
ld_script_input_file(struct ld *ld, unsigned as_needed, void *in)
{
	struct ld_script_input_file *ldif;

	if ((ldif = calloc(1, sizeof(*ldif))) == NULL)
		ld_fatal_std(ld, "calloc");
	ldif->ldif_as_needed = as_needed;
	if (as_needed)
		ldif->ldif_u.ldif_ldl = in;
	else
		ldif->ldif_u.ldif_name = in;

	return (ldif);
}

struct ld_script_list *
ld_script_list(struct ld *ld, struct ld_script_list *list, void *entry)
{
	struct ld_script_list *ldl;

	if ((ldl = malloc(sizeof(*ldl))) == NULL)
		ld_fatal_std(ld, "malloc");
	ldl->ldl_entry = entry;
	ldl->ldl_next = list;

	return (ldl);
}

void
ld_script_list_free(struct ld_script_list *list, void (*_free)(void *ptr))
{
	struct ld_script_list *ldl;

	if (list == NULL)
		return;

	do {
		ldl = list;
		list = ldl->ldl_next;
		if (ldl->ldl_entry)
			_free(ldl->ldl_entry);
		free(ldl);
	} while (list != NULL);
}

struct ld_script_list *
ld_script_list_reverse(struct ld_script_list *list)
{
	struct ld_script_list *root, *next;

	root = NULL;
	while (list != NULL) {
		next = list->ldl_next;
		list->ldl_next = root;
		root = list;
		list = next;
	}

	return (root);
}

void
ld_script_nocrossrefs(struct ld *ld, struct ld_script_list *list)
{
	struct ld_script_nocrossref *ldn;

	if ((ldn = calloc(1, sizeof(*ldn))) == NULL)
		ld_fatal_std(ld, "calloc");
	ldn->ldn_l = list;
	STAILQ_INSERT_TAIL(&ld->ld_scp->lds_n, ldn, ldn_next);
}

struct ld_script_phdr *
ld_script_phdr(struct ld *ld, char *name, char *type, unsigned filehdr,
    unsigned phdrs, struct ld_exp *addr, unsigned flags)
{
	struct ld_script_phdr *ldsp;

	if ((ldsp = calloc(1, sizeof(*ldsp))) == NULL)
		ld_fatal_std(ld, "calloc");

	ldsp->ldsp_name = name;
	ldsp->ldsp_type = type;
	ldsp->ldsp_filehdr = filehdr;
	ldsp->ldsp_phdrs = phdrs;
	ldsp->ldsp_addr = addr;
	ldsp->ldsp_flags = flags;

	return (ldsp);
}

struct ld_script_region *
ld_script_region(struct ld *ld, char *name, char *attr, struct ld_exp *origin,
    struct ld_exp *len)
{
	struct ld_script_region *ldsr;

	if ((ldsr = malloc(sizeof(*ldsr))) == NULL)
		ld_fatal_std(ld, "malloc");

	ldsr->ldsr_name = name;
	ldsr->ldsr_attr = attr;
	ldsr->ldsr_origin = origin;
	ldsr->ldsr_len = len;

	return (ldsr);
}

void
ld_script_region_alias(struct ld *ld, char *alias, char *region)
{
	struct ld_script_region_alias *ldra;

	if ((ldra = calloc(1, sizeof(*ldra))) == NULL)
		ld_fatal_std(ld, "calloc");

	ldra->ldra_alias = alias;
	ldra->ldra_region = region;

	STAILQ_INSERT_TAIL(&ld->ld_scp->lds_a, ldra, ldra_next);
}

void
ld_script_version_add_node(struct ld *ld, char *ver, void *head, char *depend)
{
	struct ld_script_version_node *ldvn;

	if ((ldvn = calloc(1, sizeof(*ldvn))) == NULL)
		ld_fatal_std(ld, "calloc");
	
	ldvn->ldvn_name = ver;
	if (ldvn->ldvn_name == NULL) {
		/*
		 * Version name can be omitted only when this is the only
		 * node in the version script.
		 */
		if (ld->ld_scp->lds_vn_name_omitted ||
		    !STAILQ_EMPTY(&ld->ld_scp->lds_vn))
			ld_fatal(ld, "version script can only have one "
			    "version node that is without a version name");
		ld->ld_scp->lds_vn_name_omitted = 1;
	}

	ldvn->ldvn_dep = depend;
	ldvn->ldvn_e = head;

	STAILQ_INSERT_TAIL(&ld->ld_scp->lds_vn, ldvn, ldvn_next);
}

struct ld_script_version_entry *
ld_script_version_alloc_entry(struct ld *ld, char *sym, void *extern_block)
{
	struct ld_state *ls;
	struct ld_script_version_entry *ldve;
	int ignore;
	char *p;

	ls = &ld->ld_state;

	if ((ldve = calloc(1, sizeof(*ldve))) == NULL)
		ld_fatal_std(ld, "calloc");

	ldve->ldve_sym = sym;
	ldve->ldve_local = ls->ls_version_local;
	ldve->ldve_list = extern_block;

	if (ldve->ldve_sym == NULL)
		return (ldve);

	ignore = 0;
	for (p = ldve->ldve_sym; *p != '\0'; p++) {
		switch (*p) {
		case '\\':
			/* Ignore the next char */
			ignore = 1;
			break;
		case '?':
		case '*':
		case '[':
			if (!ignore) {
				ldve->ldve_glob = 1;
				goto done;
			} else
				ignore = 0;
		}
	}

done:
	return (ldve);
}

void *
ld_script_version_link_entry(struct ld *ld,
    struct ld_script_version_entry_head *head,
    struct ld_script_version_entry *ldve)
{

	if (ldve == NULL)
		return (head);

	if (head == NULL) {
		if ((head = calloc(1, sizeof(*head))) == NULL)
			ld_fatal_std(ld, "calloc");
		STAILQ_INIT(head);
	}

	if (ldve->ldve_list != NULL) {
		STAILQ_CONCAT(head, ldve->ldve_list);
		free(ldve->ldve_list);
		free(ldve);
	} else
		STAILQ_INSERT_TAIL(head, ldve, ldve_next);

	return (head);
}

void
ld_script_version_set_lang(struct ld * ld,
    struct ld_script_version_entry_head *head, char *lang)
{
	struct ld_script_version_entry *ldve;
	enum ld_script_version_lang vl;

	vl = VL_C;

	if (!strcasecmp(lang, "c"))
		vl = VL_C;
	else if (!strcasecmp(lang, "c++"))
		vl = VL_CPP;
	else if (!strcasecmp(lang, "java"))
		vl = VL_JAVA;
	else
		ld_warn(ld, "unrecognized language `%s' in version script",
		    lang);

	STAILQ_FOREACH(ldve, head, ldve_next) {
		/* Do not overwrite lang set by inner extern blocks. */
		if (!ldve->ldve_lang_set) {
			ldve->ldve_lang = vl;
			ldve->ldve_lang_set = 1;
		}
	}
}
    

static void
_input_file_add(struct ld *ld, struct ld_script_input_file *ldif)
{
	struct ld_state *ls;
	struct ld_script_list *ldl;
	unsigned old_as_needed;

	ls = &ld->ld_state;

	if (!ldif->ldif_as_needed) {
		ld_file_add(ld, ldif->ldif_u.ldif_name, LFT_UNKNOWN);
		free(ldif->ldif_u.ldif_name);
	} else {
		old_as_needed = ls->ls_as_needed;
		ls->ls_as_needed = 1;
		ldl = ldif->ldif_u.ldif_ldl;
		while (ldl != NULL) {
			ld_file_add(ld, ldl->ldl_entry, LFT_UNKNOWN);
			ldl = ldl->ldl_next;
		}
		ls->ls_as_needed = old_as_needed;
		ld_script_list_free(ldif->ldif_u.ldif_ldl, free);
	}
}

static struct ld_script_variable *
_variable_find(struct ld *ld, char *name)
{
	struct ld_script_variable *ldv;

	HASH_FIND_STR(ld->ld_scp->lds_v, name, ldv);

	return (ldv);
}
